# EmbeddableMappingBuilder.java

    package org.codefilarete.stalactite.engine.configurer.builder.embeddable;

    import java.lang.reflect.Method;
    import java.util.ArrayDeque;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    import java.util.Queue;
    import java.util.Set;
    import java.util.function.Consumer;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    import javax.annotation.Nullable;

    import org.codefilarete.reflection.Accessor;
    import org.codefilarete.reflection.AccessorChain;
    import org.codefilarete.reflection.AccessorDefinition;
    import org.codefilarete.reflection.PropertyAccessPoint;
    import org.codefilarete.reflection.ReadWriteAccessorChain;
    import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
    import org.codefilarete.reflection.ReversibleAccessor;
    import org.codefilarete.reflection.ReversibleMutator;
    import org.codefilarete.reflection.ValueAccessPoint;
    import org.codefilarete.reflection.ValueAccessPointComparator;
    import org.codefilarete.reflection.ValueAccessPointMap;
    import org.codefilarete.reflection.ValueAccessPointSet;
    import org.codefilarete.stalactite.dsl.MappingConfigurationException;
    import org.codefilarete.stalactite.dsl.key.CompositeKeyMappingConfiguration;
    import org.codefilarete.stalactite.dsl.naming.ColumnNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.UniqueConstraintNamingStrategy;
    import org.codefilarete.stalactite.sql.ddl.Size;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.statement.SQLStatement.BindingException;
    import org.codefilarete.stalactite.sql.statement.binder.ColumnBinderRegistry;
    import org.codefilarete.stalactite.sql.statement.binder.ParameterBinder;
    import org.codefilarete.stalactite.sql.statement.binder.ParameterBinderRegistry.EnumBindType;
    import org.codefilarete.tool.Duo;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.VisibleForTesting;
    import org.codefilarete.tool.bean.Objects;
    import org.codefilarete.tool.collection.Arrays;
    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.collection.Maps;
    import org.codefilarete.tool.exception.NotImplementedException;
    import org.codefilarete.tool.function.Converter;
    import org.codefilarete.tool.function.Hanger.Holder;

    import static org.codefilarete.reflection.MethodReferences.toMethodReferenceString;
    import static org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMappingConfiguration.EmbeddableLinkageSupport;
    import static org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMappingConfiguration.fromCompositeKeyMappingConfiguration;
    import static org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMappingConfiguration.fromEmbeddableMappingConfiguration;
    import static org.codefilarete.tool.Nullable.nullable;
    import static org.codefilarete.tool.collection.Iterables.stream;

    /**
     * Engine that converts mapping definition of a {@link EmbeddableMappingConfiguration} to a simple {@link Map}.
     * Designed as such as its instances can be reused (no constructor, attributes are set through
     * {@link #build()}
     * therefore they are not thread-safe. This was made to avoid extra instance creation because this class is used in some loops.
     *
     * Whereas it consumes an {@link EmbeddableMappingConfiguration} it doesn't mean that its goal is to manage embedded beans of an entity : as its
     * name says it's aimed at collecting mapping of any beans, without the entity part (understanding identification and inheritance which is targeted
     * by {@link org.codefilarete.stalactite.engine.configurer.builder.DefaultPersisterBuilder})
     *
     * @author Guillaume Mary
     * @see #build()
     */
    public class EmbeddableMappingBuilder<C, T extends Table<T>> {

    	/**
    	 * Iterates over configuration to look for any property defining a {@link Column} to use, its table would be the one to be used by builder.
    	 * Throws an exception if several tables are found during iteration.
    	 *
    	 * @param mappingConfiguration the configuration to look up for any overriding {@link Column}
    	 * @return null if no {@link Table} was found (meaning that builder is free to create one)
    	 */
    	public static Table giveTargetTable(org.codefilarete.stalactite.dsl.embeddable.EmbeddableMappingConfiguration<?> mappingConfiguration) {
    		Holder<Table> result = new Holder<>();

    		// algorithm close to the one of includeEmbeddedMapping(..)
    		Queue<Inset> stack = new ArrayDeque<>(fromEmbeddableMappingConfiguration(mappingConfiguration).getInsets());
    		while (!stack.isEmpty()) {
    			Inset<?, ?> inset = stack.poll();
    			inset.getOverriddenColumns().forEach((valueAccessPoint, targetColumn) ->
    				assertHolderIsFilledWithTargetTable(result, targetColumn, valueAccessPoint)
    			);
    			org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMappingConfiguration<?> configuration = inset.getConfiguration();
    			stack.addAll(configuration.getInsets());
    		}
    		return result.get();
    	}

    	public static Table giveTargetTable(org.codefilarete.stalactite.dsl.embeddable.EmbeddableMappingConfiguration<?> mappingConfiguration, Table mainTable) {
    		Holder<Table> result = new Holder<>(mainTable);

    		// algorithm close to the one of includeEmbeddedMapping(..)
    		Queue<Inset> stack = new ArrayDeque<>(fromEmbeddableMappingConfiguration(mappingConfiguration).getInsets());
    		while (!stack.isEmpty()) {
    			Inset<?, ?> inset = stack.poll();
    			inset.getOverriddenColumns().forEach((valueAccessPoint, targetColumn) ->
    					assertHolderIsFilledWithTargetTable(result, targetColumn, valueAccessPoint)
    			);
    			org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMappingConfiguration<?> configuration = inset.getConfiguration();
    			stack.addAll(configuration.getInsets());
    		}
    		return result.get();
    	}

    	private static void assertHolderIsFilledWithTargetTable(Holder<Table> result, Column targetColumn, ValueAccessPoint<?> valueAccessPoint) {
    		if (targetColumn != null) {
    			if (result.get() != null && result.get() != targetColumn.getTable()) {
    				throw new MappingConfigurationException("Property " + valueAccessPoint + " overrides column with " + targetColumn.getAbsoluteName() + " but it is not part of main table " + result.get().getAbsoluteName());
    			}
    			result.set(targetColumn.getTable());
    		}
    	}

    	/**
    	 * Composes an {@link AccessorChain} from given {@link ValueAccessPoint}.
    	 * Acts as a caster or converter because {@link ValueAccessPoint} is an abstract vision of elements accepted by
    	 * {@link AccessorChain}.
    	 * Supported {@link ValueAccessPoint}s are {@link Accessor} and {@link ReversibleAccessor}, else an exception will be thrown
    	 *
    	 * @param rootAccessors first elements to access a property
    	 * @param terminalAccessor final accessor that read property of latest element of rootAccessors
    	 * @param beanType read property type
    	 * @return a new {@link AccessorChain} that access property of terminalAccessor thanks to all accessors of rootAccessors
    	 */
    	private static <SRC, TRGT> ReadWritePropertyAccessPoint<SRC, TRGT> newAccessorChain(List<? extends ValueAccessPoint<?>> rootAccessors,
    																		 ValueAccessPoint<?> terminalAccessor,
    																		 Class<TRGT> beanType) {
    		// we must clone rootAccessors to prevent from accessor mixing
    		List<ValueAccessPoint<?>> accessorsTmp = new ArrayList<>(rootAccessors);
    		accessorsTmp.add(terminalAccessor);
    		List<Accessor<?, ?>> finalAccessors = new ArrayList<>();
    		accessorsTmp.forEach(valueAccessPoint -> {
    			if (valueAccessPoint instanceof Accessor) {
    				finalAccessors.add((Accessor) valueAccessPoint);
    			} else if (valueAccessPoint instanceof ReversibleMutator) {
    				finalAccessors.add(((ReversibleMutator) valueAccessPoint).toAccessor());
    			} else {
    				// Something has change in ValueAccessPoint hierarchy, it should be fixed (grave)
    				throw new NotImplementedException(Reflections.toString(valueAccessPoint.getClass()) + " is unknown from chain creation algorithm");
    			}
    		});
    		return new ReadWriteAccessorChain<>(
    				AccessorChain.fromAccessorsWithNullSafe(
    						finalAccessors,
    						// this can look superfluous but fills the gap of instantiating right bean when configuration is a subtype of inset accessor,
    						// case which is allowed by signature of embed(..) method : it accepts "? extend T" as parameter type of given configuration
    						// (where T is type returned by accessor, or expected as input of mutator)
    						(accessor, aClass) -> Reflections.newInstance(beanType)));
    	}

    	private final EmbeddableMappingConfiguration<C> mainMappingConfiguration;
    	private final T targetTable;
    	private final ColumnBinderRegistry columnBinderRegistry;
    	private final ColumnNamingStrategy columnNamingStrategy;
    	private final UniqueConstraintNamingStrategy uniqueConstraintNamingStrategy;

    	public EmbeddableMappingBuilder(org.codefilarete.stalactite.dsl.embeddable.EmbeddableMappingConfiguration<C> mappingConfiguration,
    									T targetTable,
    									ColumnBinderRegistry columnBinderRegistry,
    									ColumnNamingStrategy columnNamingStrategy,
    									UniqueConstraintNamingStrategy uniqueConstraintNamingStrategy) {
    		this(fromEmbeddableMappingConfiguration(mappingConfiguration),
    				targetTable,
    				columnBinderRegistry,
    				columnNamingStrategy,
    				uniqueConstraintNamingStrategy);
    	}

    	public EmbeddableMappingBuilder(CompositeKeyMappingConfiguration<C> mappingConfiguration,
    									T targetTable,
    									ColumnBinderRegistry columnBinderRegistry,
    									ColumnNamingStrategy columnNamingStrategy,
    									UniqueConstraintNamingStrategy uniqueConstraintNamingStrategy) {
    		this(fromCompositeKeyMappingConfiguration(mappingConfiguration),
    				targetTable,
    				columnBinderRegistry,
    				columnNamingStrategy,
    				uniqueConstraintNamingStrategy);
    	}

    	@VisibleForTesting
    	EmbeddableMappingBuilder(EmbeddableMappingConfiguration<C> mappingConfiguration,
    							 T targetTable,
    							 ColumnBinderRegistry columnBinderRegistry,
    							 ColumnNamingStrategy columnNameStrategy,
    							 UniqueConstraintNamingStrategy uniqueConstraintNamingStrategy) {
    		this.mainMappingConfiguration = mappingConfiguration;
    		this.targetTable = targetTable;
    		this.columnNamingStrategy = columnNameStrategy;
    		this.columnBinderRegistry = columnBinderRegistry;
    		this.uniqueConstraintNamingStrategy = uniqueConstraintNamingStrategy;
    	}

    	/**
    	 * Converts mapping definition of a {@link EmbeddableMappingConfiguration} into a simple {@link Map}
    	 *
    	 * @return a bean that stores some {@link Map}s representing the definition of the mapping declared by the {@link EmbeddableMappingConfiguration}
    	 */
    	public EmbeddableMapping<C, T> build() {
    		return build(false);
    	}

    	public EmbeddableMapping<C, T> build(boolean onlyExtraTableLinkages) {
    		return build(onlyExtraTableLinkages, new ValueAccessPointSet<>());
    	}

    	private EmbeddableMapping<C, T> build(boolean onlyExtraTableLinkages, ValueAccessPointSet<C, ValueAccessPoint<C>> excludedProperties) {
    		InternalProcessor internalProcessor = new InternalProcessor(onlyExtraTableLinkages);
    		// converting direct mapping
    		internalProcessor.includeDirectMapping(this.mainMappingConfiguration,
    				null,
    				new ValueAccessPointMap<>(),
    				new ValueAccessPointMap<>(),
    				new ValueAccessPointMap<>(),
    				excludedProperties);
    		// adding embeddable (no particular thought about order compared to previous direct mapping)
    		internalProcessor.includeEmbeddedMapping();
    		return internalProcessor.result;
    	}

    	protected T getTargetTable() {
    		return targetTable;
    	}

    	protected <O> String determineColumnName(EmbeddableLinkage<C, O> linkage, @Nullable String overriddenColumName) {
    		return nullable(overriddenColumName)
    				.elseSet(linkage::getColumnName)
    				.elseSet(linkage::getFieldName)
    				.elseSet(() -> columnNamingStrategy.giveName(AccessorDefinition.giveDefinition(linkage.getAccessor())))
    				.get();
    	}

    	protected <O> Size determineColumnSize(EmbeddableLinkage<C, O> linkage, @Nullable Size overriddenColumSize) {
    		return nullable(overriddenColumSize).elseSet(linkage::getColumnSize).get();
    	}

    	/**
    	 * Internal engine driven by {@link #build()} method.
    	 * Made to store result in another class than main one and decouple configuration from process.
    	 *
    	 * @author Guillaume Mary
    	 */
    	protected class InternalProcessor {

    		private final EmbeddableMapping<C, T> result;

    		private final boolean onlyExtraTableLinkages;

    		protected InternalProcessor(boolean onlyExtraTableLinkages) {
    			this.onlyExtraTableLinkages = onlyExtraTableLinkages;
    			this.result = new EmbeddableMapping<>(mainMappingConfiguration.getBeanType());
    		}

    		protected void includeDirectMapping(EmbeddableMappingConfiguration<?> mappingConfiguration,
    											@Nullable ValueAccessPoint<C> accessorPrefix,
    											ValueAccessPointMap<C, String, ValueAccessPoint<C>> overriddenColumnNames,
    											ValueAccessPointMap<C, Size, ValueAccessPoint<C>> overriddenColumnSizes,
    											ValueAccessPointMap<C, Column<T, ?>, ValueAccessPoint<C>> overriddenColumns,
    											ValueAccessPointSet<C, ValueAccessPoint<C>> excludedProperties) {
    			Stream<EmbeddableLinkage> linkageStream = ((List<EmbeddableLinkage>) (List) mappingConfiguration.getPropertiesMapping()).stream()
    					.filter(linkage -> !excludedProperties.contains(linkage.getAccessor()));

    			if (!onlyExtraTableLinkages) {
    				// this method (and class) doesn't deal with extra table
    				linkageStream = linkageStream.filter(linkage -> linkage.getExtraTable() == null);
    			}
    			linkageStream.forEach(linkage -> {
    				Column<T, ?> overriddenColumn = overriddenColumns.get(linkage.getAccessor());
    				String columnName = nullable(overriddenColumn)
    						.map(Column::getName)
    						.getOr(() -> determineColumnName(linkage, overriddenColumnNames.get(linkage.getAccessor())));
    				Size columnSize = nullable(overriddenColumn)
    						.map(Column::getSize)
    						.getOr(() -> determineColumnSize(linkage, overriddenColumnSizes.get(linkage.getAccessor())));
    				assertMappingIsNotAlreadyDefinedByInheritance(linkage, columnName, mappingConfiguration);
    				Duo<ReadWritePropertyAccessPoint<C, Object>, Column<T, Object>> mapping = includeMapping(
    						linkage,
    						accessorPrefix,
    						columnName,
    						columnSize,
    						overriddenColumn,
    						mappingConfiguration);
    				Converter<Object, Object> readConverter = linkage.getReadConverter();
    				result.getReadConverters().put(mapping.getLeft(), readConverter);
    				if (!linkage.isReadonly()) {
    					result.getMapping().put(mapping.getLeft(), mapping.getRight());
    					Converter<Object, Object> writeConverter = linkage.getWriteConverter();
    					result.getWriteConverters().put(mapping.getLeft(), writeConverter);
    				} else {
    					result.getReadonlyMapping().put(mapping.getLeft(), mapping.getRight());
    				}
    			});
    		}

    		protected <O> Duo<ReadWritePropertyAccessPoint<C, ?>, Column<T, Object>> includeMapping(EmbeddableLinkage<C, O> linkage,
    																								@Nullable ValueAccessPoint<C> accessorPrefix,
    																								String columnName,
    																								@Nullable Size columnSize,
    																								@Nullable Column<T, O> overriddenColumn,
    																								EmbeddableMappingConfiguration<?> beanMappingConfiguration) {
    			Column<T, O> column = nullable(overriddenColumn).getOr(() -> addColumnToTable(linkage, columnName, columnSize));

    			if (linkage.isUnique() && linkage instanceof EmbeddableLinkageSupport) {
    				targetTable.addUniqueConstraint(
    								Objects.preventNull(beanMappingConfiguration.getUniqueConstraintNamingStrategy(), uniqueConstraintNamingStrategy).giveName(linkage.getAccessor(), column),
    								column);
    			}

    			ensureColumnBindingInRegistry(linkage, column);
    			ReadWritePropertyAccessPoint<C, ?> accessor;
    			if (accessorPrefix != null) {
    				accessor = newAccessorChain(Collections.singletonList(accessorPrefix), linkage.getAccessor(), beanMappingConfiguration.getBeanType());
    			} else {
    				accessor = linkage.getAccessor();
    			}
    			return new Duo<>(accessor, (Column<T, Object>) column);
    		}

    		protected <O> void assertMappingIsNotAlreadyDefinedByInheritance(EmbeddableLinkage<C, O> linkage, String columnNameToCheck, EmbeddableMappingConfiguration<O> mappingConfiguration) {
    			DuplicateDefinitionChecker duplicateDefinitionChecker = new DuplicateDefinitionChecker(columnNameToCheck, linkage.getAccessor(), columnNamingStrategy);
    			stream(mappingConfiguration.inheritanceIterable())
    					.flatMap(configuration -> (Stream<EmbeddableLinkage>) configuration.getPropertiesMapping().stream())
    					// not using equals() is voluntary since we want reference checking here to exclude same instance,
    					// since given linkage is one of given mappingConfiguration
    					// (doing as such also prevent equals() method override to break this algorithm)
    					.filter(pawn -> linkage != pawn
    							// only writable properties are concerned by this check : we allow duplicates for readonly properties
    							&& !pawn.isReadonly() && !linkage.isReadonly())
    					.forEach(duplicateDefinitionChecker);
    		}

    		protected <O> Column<T, O> addColumnToTable(EmbeddableLinkage<C, O> linkage, String columnName, @Nullable Size columnSize) {
    			Column<T, O> addedColumn;
    			Class<O> columnType;
    			if (linkage.getColumnType().isEnum()) {
    				if (linkage.getEnumBindType() == null) {
    					columnType = (Class<O>) Enum.class;
    				} else {
    					columnType = linkage.getEnumBindType() == EnumBindType.NAME
    							? (Class<O>) String.class
    							: (Class<O>) Integer.class;
    				}
    			} else {
    				if (linkage.getParameterBinder() != null) {
    					// when a parameter binder is defined, then the column type must be binder one
    					columnType = linkage.getParameterBinder().getColumnType();
    				} else {
    					columnType = linkage.getColumnType();
    				}
    			}
    			// if user ask for nullability, then we follow his demand, else we rely on property type: primitive ones are mandatory
    			Boolean isColumnNullable =
    					nullable(linkage.isNullable()).getOr(() -> Reflections.isPrimitiveType(linkage.getColumnType()) ? false : null);
    			addedColumn = targetTable.addColumn(columnName, columnType, columnSize, isColumnNullable);
    			return addedColumn;
    		}

    		protected <O> void ensureColumnBindingInRegistry(EmbeddableLinkage<C, O> linkage, Column<?, O> column) {
    			if (linkage.getColumnType().isEnum()) {
    				EnumBindType enumBindType = Objects.preventNull(linkage.getEnumBindType(), EnumBindType.ORDINAL);
    				columnBinderRegistry.register(column, enumBindType.newParameterBinder((Class<Enum>) linkage.getColumnType()));
    			} else if (linkage.getParameterBinder() != null) {
    				columnBinderRegistry.register(column, (ParameterBinder<O>) linkage.getParameterBinder());
    			} else {
    				try {
    					// assertion to check that column binder is registered : it will throw en exception if the binder is not found
    					columnBinderRegistry.getBinder(column);
    				} catch (BindingException e) {
    					throw new MappingConfigurationException("No binder found for property " + AccessorDefinition.toString(linkage.getAccessor())
    							+ " : neither its column nor its type are registered (" + column.getAbsoluteName() + ", type " + Reflections.toString(column.getJavaType()) + ")", e);
    				}
    			}
    		}

    		/**
    		 * Adds embedded beans mapping to result
    		 */
    		private void includeEmbeddedMapping() {
    			Set<Inset<C, ?>> treatedInsets = new HashSet<>();

    			Queue<Inset<C, ?>> stack = Collections.asLifoQueue(new ArrayDeque<>());
    			stack.addAll(mainMappingConfiguration.getInsets());
    			Queue<ReadWritePropertyAccessPoint<C, ?>> accessorPath = new ArrayDeque<>();
    			while (!stack.isEmpty()) {
    				Inset<C, ?> inset = stack.poll();

    				assertNotAlreadyDeclared(inset, treatedInsets);

    				EmbeddableMappingConfiguration<C> configuration = (EmbeddableMappingConfiguration<C>) inset.getConfiguration();
    				PropertyAccessPoint<C, ?> mappingPrefix = null;
    				if (inset.getAccessor() != null) {
    					accessorPath.add(inset.getAccessor());
    					// small optimization to avoid creation of an Accessor chain of 1 element
    					if (accessorPath.size() == 1) {
    						mappingPrefix = accessorPath.peek();
    					} else {
    						mappingPrefix = new ReadWriteAccessorChain<>(AccessorChain.fromAccessorsWithNullSafe(new ArrayList<>(accessorPath)));
    					}
    				}
    				EmbeddableMappingConfiguration<?> superClassConfiguration = configuration.getMappedSuperClassConfiguration();
    				if (superClassConfiguration != null) {
    					includeMappedSuperClassMapping(inset, accessorPath, superClassConfiguration);
    				}

    				includeDirectMapping(configuration,
    						mappingPrefix,
    						(ValueAccessPointMap) inset.getOverriddenColumnNames(),
    						(ValueAccessPointMap) inset.getOverriddenColumnSizes(),
    						(ValueAccessPointMap) inset.getOverriddenColumns(),
    						(ValueAccessPointSet) inset.getExcludedProperties());
    				if (configuration.getInsets().isEmpty()) {
    					accessorPath.remove();
    				} else {
    					stack.addAll(configuration.getInsets());
    				}
    				treatedInsets.add(inset);
    			}
    		}

    		private void includeMappedSuperClassMapping(Inset<C, ?> inset, Collection<ReadWritePropertyAccessPoint<C, ?>> accessorPath, EmbeddableMappingConfiguration<?> superClassConfiguration) {
    			// we include super type mapping by using a new instance of EmbeddableMappingBuilder, this is the simplest (but maybe not the most
    			// debuggable) and allows to manage inheritance of several mappedSuperClass
    			ValueAccessPointSet excludedProperties = new ValueAccessPointSet<>();
    			// we remove overridden inset columns to avoid their creation by the EmbeddableMappingBuilder
    			// to avoid duplicates because they are already in the target table (through their creation) and the builder
    			// will create them with their default name
    			excludedProperties.addAll(inset.getOverriddenColumns().keySet());
    			EmbeddableMappingBuilder<C, T> mappedSuperClassBuilder = new EmbeddableMappingBuilder<C, T>((EmbeddableMappingConfiguration<C>) superClassConfiguration, targetTable,
    					columnBinderRegistry, columnNamingStrategy, Objects.preventNull(superClassConfiguration.getUniqueConstraintNamingStrategy(), uniqueConstraintNamingStrategy)) {

    				@Override
    				protected <O> String determineColumnName(EmbeddableLinkage<C, O> linkage, @Nullable String overriddenColumName) {
    					return super.determineColumnName(linkage, inset.getOverriddenColumnNames().get(linkage.getAccessor()));
    				}

    				@Override
    				protected <O> Size determineColumnSize(EmbeddableLinkage<C, O> linkage, @Nullable Size overriddenColumSize) {
    					return super.determineColumnSize(linkage, inset.getOverriddenColumnSizes().get(linkage.getAccessor()));
    				}
    			};

    			EmbeddableMapping<C, T> superMapping = mappedSuperClassBuilder.build(false, excludedProperties);
    			Class<?> insetBeanType = inset.getConfiguration().getBeanType();
    			superMapping.getMapping().forEach((accessor, column) -> {
    				List<ReadWritePropertyAccessPoint<?, ?>> accessors;
    				if (accessorPath.size() == 1) {
    					accessors = Arrays.asList(Iterables.first(accessorPath), accessor);
    				} else {
    					accessors = new ArrayList<>(accessorPath);
    					accessors.add(accessor);
    				}

    				ReadWritePropertyAccessPoint<C, Object> prefix = new ReadWriteAccessorChain<>(AccessorChain.fromAccessorsWithNullSafe(
    						accessors,
    						// this can look superfluous but fills the gap with instantiating right bean when configuration is a subtype of inset accessor,
    						// case which is allowed by signature of embed(..) method : it accepts "? extend T" as parameter type of given configuration
    						// (where T is type returned by accessor, or expected as input of mutator)
    						(localAccessor, accessorInputType) -> Reflections.newInstance(insetBeanType)));

    				// Computing definitive column because it may be overridden by inset declaration
    				Column<T, ?> finalColumn;
    				if (inset.getOverriddenColumns().containsKey(accessor)) {
    					finalColumn = inset.getOverriddenColumns().get(accessor);
    				} else if (inset.getOverriddenColumnNames().containsKey(accessor)) {
    					finalColumn = targetTable.addColumn(inset.getOverriddenColumnNames().get(accessor), column.getJavaType());
    				} else {
    					finalColumn = targetTable.addColumn(column.getName(), column.getJavaType());
    				}
    				result.getMapping().put(prefix, finalColumn);
    			});
    		}

    		/**
    		 * Ensures that a bean is not already embedded with same accessor, because its columns would conflict with already defined ones, or checks that every property
    		 * is overridden.
    		 * Throws an exception if that's not the case.
    		 *
    		 * @param inset current inset to be checked for duplicate
    		 * @param treatedInsets already mapped insets : if one of them matches given inset on mapped type, then a fine-grained check is done to look for conflict
    		 */
    		private void assertNotAlreadyDeclared(Inset<C, ?> inset, Set<Inset<C, ?>> treatedInsets) {
    			Optional<Inset<C, ?>> alreadyMappedType = treatedInsets.stream().filter(i -> i.getEmbeddedClass() == inset.getEmbeddedClass()).findFirst();
    			if (alreadyMappedType.isPresent()) {
    				// accessors are exactly the same ?
    				if (alreadyMappedType.get().getInsetAccessor().equals(inset.getInsetAccessor())) {
    					Method currentMethod = inset.getInsetAccessor();
    					String currentMethodReference = toMethodReferenceString(currentMethod);
    					throw new MappingConfigurationException(currentMethodReference + " is already mapped");
    				}

    				Map<String, ValueAccessPoint<?>> columNamePerAccessPoint = new HashMap<>();
    				EmbeddableMappingConfiguration<?> insetConfiguration = inset.getConfiguration();
    				((List<EmbeddableLinkage>) (List) insetConfiguration.getPropertiesMapping()).forEach(linkage -> {
    					if (!inset.getExcludedProperties().contains(linkage.getAccessor())) {
    						String columnName = determineColumnName(linkage, inset.getOverriddenColumnNames().get(linkage.getAccessor()));
    						columNamePerAccessPoint.put(columnName, linkage.getAccessor());
    					}
    				});
    				Inset<?, ?> abstractInset = alreadyMappedType.get();
    				Map<String, ValueAccessPoint<?>> columNamePerAccessPoint2 = new HashMap<>();
    				((List<EmbeddableLinkage>) (List) insetConfiguration.getPropertiesMapping()).forEach(linkage -> {
    					if (!abstractInset.getExcludedProperties().contains(linkage.getAccessor())) {
    						String columnName = determineColumnName(linkage, abstractInset.getOverriddenColumnNames().get(linkage.getAccessor()));
    						columNamePerAccessPoint2.put(columnName, linkage.getAccessor());
    					}
    				});
    				Map<ValueAccessPoint<?>, ValueAccessPoint<?>> join = Maps.innerJoin(columNamePerAccessPoint, columNamePerAccessPoint2);
    				if (!join.isEmpty()) {
    					String currentMethodReference = toMethodReferenceString(inset.getInsetAccessor());
    					String conflictingDeclaration = toMethodReferenceString(abstractInset.getInsetAccessor());
    					throw new MappingConfigurationException(
    							currentMethodReference + " conflicts with " + conflictingDeclaration + " while embedding a " + Reflections.toString(inset.getEmbeddedClass())
    									+ ", column names should be overridden : "
    									+ join.keySet()
    									.stream().map(AccessorDefinition::toString).collect(Collectors.joining(", ")));
    				}
    			}
    		}
    	}

    	static class DuplicateDefinitionChecker implements Consumer<EmbeddableLinkage> {

    		private final String columnNameToCheck;
    		private final PropertyAccessPoint propertyAccessor;
    		private final ColumnNamingStrategy columnNameStrategy;
    		private static final ValueAccessPointComparator VALUE_ACCESS_POINT_COMPARATOR = new ValueAccessPointComparator();

    		DuplicateDefinitionChecker(String columnNameToCheck, PropertyAccessPoint propertyAccessor, ColumnNamingStrategy columnNameStrategy) {
    			this.columnNameToCheck = columnNameToCheck;
    			this.propertyAccessor = propertyAccessor;
    			this.columnNameStrategy = columnNameStrategy;
    		}
    		@Override
    		public void accept(EmbeddableLinkage pawn) {
    			PropertyAccessPoint accessor = pawn.getAccessor();
    			if (VALUE_ACCESS_POINT_COMPARATOR.compare(accessor, propertyAccessor) == 0) {
    				throw new MappingConfigurationException("Mapping is already defined by method " + AccessorDefinition.toString(propertyAccessor));
    			} else if (columnNameToCheck.equals(pawn.getColumnName())
    						|| columnNameToCheck.equals(columnNameStrategy.giveName(AccessorDefinition.giveDefinition(accessor)))) {
    				throw new MappingConfigurationException("Column '" + columnNameToCheck + "' of mapping '" + AccessorDefinition.toString(propertyAccessor)
    						+ "' is already targeted by '" + AccessorDefinition.toString(pawn.getAccessor()) + "'");
    			}
    		}
    	}
    }